// Copyright (C) 2006-2007 - Ivan Zhakov (chemodax@gmail.com)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

using System;
using System.IO;
using System.Text;
using System.Collections;

namespace CTCDecoder
{
    class CTCDecoder
    {
        public class MenuItem
        {
            public IDPair Menu = new IDPair();
            public IDPair RelativeGroup = new IDPair();
            public short Priority;
            public string CommandName;
        }
        
        public class GroupItem
        {
            public IDPair Group = new IDPair();
            public IDPair RelativeGroup = new IDPair();
            public short Priority;
        }

        public class CommandItem
        {
            public IDPair Command = new IDPair();
            public IDPair RelativeGroup = new IDPair();
            public string CommandName;
        }

        public class PlacementItem
        {
            public IDPair Command = new IDPair();
            public IDPair RelativeGroup = new IDPair();
            public short Priority;
        }

        static string ReadPaddedString(BinaryReader reader)
        {
            short len = reader.ReadInt16();
            // Console.WriteLine("Len: {0}", len.ToString());
            return new string(reader.ReadChars((len+3)/4*4/2));
        }

        static string ReadTerminatedString(BinaryReader reader)
        {
            StringBuilder builder = new StringBuilder();
            
            while(true)
            {
                char ch = reader.ReadChar();
                if (ch == '\0')
                    break;

                builder.Append(ch);
            }

            // Console.WriteLine("Len: {0}", builder.Length);
            return builder.ToString();
        }
        static Guid [] ReadGuidTable(BinaryReader reader)
        {
            int guidLen = reader.ReadInt32();
            Guid [] guidTable = new Guid[guidLen+1];
            for (int i = 0; i < guidLen; i++)
            {
                byte [] buf = reader.ReadBytes(16);
                guidTable[i+1] = new Guid(buf);
            }

            return guidTable;
        }

        static void PrintBytes(byte [] bytes)
        {
            foreach(byte b in bytes)
            {
                Console.Write("{0} ", b.ToString("x2"));
            }
            Console.WriteLine("");
        }
            
        static MenuItem [] ReadMenuTable(BinaryReader reader, Guid [] guidTable)
        {
            Stream file = reader.BaseStream;
            long start = file.Position - 4; // minus id

            int menuLen = reader.ReadInt32();
            ArrayList items = new ArrayList();
            
            reader.ReadBytes(12);
             
            while(file.Position + 28 < start + menuLen)
            {
                MenuItem item = new MenuItem();
                int q = reader.ReadInt16();
                item.Menu.GUID = guidTable[q];
                item.RelativeGroup.GUID = guidTable[reader.ReadInt16()];
                item.Menu.ID = reader.ReadInt32();
                item.RelativeGroup.ID = reader.ReadInt32();
                
                reader.ReadBytes(2); // Skip unknown data

                item.Priority = reader.ReadInt16();
                reader.ReadBytes(10); // Skip unknown data
                
                item.CommandName = ReadPaddedString(reader);
                if (item.CommandName.Length == 0)
                    break;

                items.Add(item);
            }

            file.Seek(start + menuLen, SeekOrigin.Begin);
            return (MenuItem []) items.ToArray(typeof(MenuItem));
        }

        static GroupItem [] ReadGroupTable(BinaryReader reader, Guid [] guidTable)
        {
            Stream file = reader.BaseStream;
            long start = file.Position - 4;

            ArrayList items = new ArrayList();

            int grupLen = reader.ReadInt32();
            
            reader.ReadBytes(12); // Skip unknown header

            while(file.Position + 20 < start + grupLen)
            {
                GroupItem item = new GroupItem();
                item.Group.GUID = guidTable[reader.ReadInt16()];
                item.RelativeGroup.GUID = guidTable[reader.ReadInt16()];
                item.Group.ID = reader.ReadInt32();
                item.RelativeGroup.ID = reader.ReadInt32();
                
                reader.ReadBytes(2); // Skip unknown data
                item.Priority = reader.ReadInt16();
                reader.ReadBytes(4); // Skip unknown data

                items.Add(item);
            }

            file.Seek(start + grupLen, SeekOrigin.Begin);
            return (GroupItem []) items.ToArray(typeof(GroupItem));
        }

        static CommandItem [] ReadCommandTable(BinaryReader reader, Guid [] guidTable)
        {
            Stream file = reader.BaseStream;
            long start = file.Position - 4;
            ArrayList items = new ArrayList();

            int cmmdLen = reader.ReadInt32();

            while(file.Position + 20 < start + cmmdLen)
            {
                CommandItem item = new CommandItem();
                //                Console.WriteLine("{0} {1}", file.Position.ToString("x"),
                //                    file.Position % 4);

                // Alignment
                reader.ReadBytes(4 - ((int) file.Position % 4));

                byte [] raw1 = reader.ReadBytes(8);
                item.Command.GUID = guidTable[reader.ReadInt16()];
                item.RelativeGroup.GUID = guidTable[reader.ReadInt16()];

                item.Command.ID = reader.ReadInt32();
                item.RelativeGroup.ID = reader.ReadInt32();

                byte [] raw2 = reader.ReadBytes(2);
                short priority = reader.ReadInt16();
                byte [] raw3 = reader.ReadBytes(8+12);

                item.CommandName = ReadTerminatedString(reader);
                
//                PrintBytes(raw1);
//                Console.WriteLine("{0} Id: {1} Pri: {2} Parent: {3}:0x{4}",
//                    name, CmdID.ToString("x"), priority.ToString("x"),
//                    parentGUID, parentID.ToString("X"));
//                PrintBytes(raw2);
//                PrintBytes(raw3);
                items.Add(item);
            }

            file.Seek(start + cmmdLen, SeekOrigin.Begin);
            return (CommandItem []) items.ToArray(typeof(CommandItem));
        }

        static PlacementItem [] ReadPlacementTable(BinaryReader reader, Guid [] guidTable)
        {
            Stream file = reader.BaseStream;
            long start = file.Position - 4;
            int len = reader.ReadInt32();
            ArrayList items = new ArrayList();

            reader.ReadBytes(12);

            while(file.Position + 16 < start + len)
            {
                byte [] raw;
                PlacementItem item = new PlacementItem();
                item.Command.GUID = guidTable[reader.ReadInt16()];
                item.RelativeGroup.GUID = guidTable[reader.ReadInt16()];
                
                item.Command.ID= reader.ReadInt32();
                item.RelativeGroup.ID = reader.ReadInt32();

                raw = reader.ReadBytes(2);
                item.Priority = reader.ReadInt16();

                items.Add(item);
            }
            
            file.Seek(start + len, SeekOrigin.Begin);
            return (PlacementItem []) items.ToArray(typeof(PlacementItem));
        }
    
        static string ReadID(Stream stream)
        {
            BinaryReader asciiReader = new BinaryReader(stream, UnicodeEncoding.ASCII);
            return new string(asciiReader.ReadChars(4));
        }

        public CTCDecoder(Stream file)
        {
            BinaryReader reader = new BinaryReader(file, UnicodeEncoding.Unicode);

            if (ReadID(file) != "MLCT")
                throw new Exception("Start ID 'MLCT' not found.");

            // Skip 16 bytes
            reader.ReadBytes(16);

            Guid [] guidTable = ReadGuidTable(reader);
            Console.Error.WriteLine("Read GUID table ({0})", guidTable.Length);            

            while(true)
            {
                string id = ReadID(file);
                if (id == String.Empty)
                {
                    break ;
                }

                Console.Error.WriteLine("Parsing section '{0}'...", id);
                
                if (id == "Menu")
                {
                    MenuTable = ReadMenuTable(reader, guidTable);
                }
                else if (id == "Grup")
                {
                    GroupTable = ReadGroupTable(reader, guidTable);
                }
                else if (id == "Cmmd")
                {
                    CommandTable = ReadCommandTable(reader, guidTable);
                }
                else if (id == "Plac")
                {
                    PlacementTable = ReadPlacementTable(reader, guidTable);
                }
                else
                {
                    long start = file.Position - 4;
                    int len = reader.ReadInt32();
                    Console.Error.WriteLine("Skiping unknown section '{0}'", id); 
                    file.Seek(start + len, SeekOrigin.Begin);
                }
            }
        }

        public readonly MenuItem [] MenuTable = new MenuItem[0];
        public readonly GroupItem [] GroupTable = new GroupItem[0];
        public readonly CommandItem [] CommandTable = new CommandItem[0];
        public readonly PlacementItem [] PlacementTable = new PlacementItem[0];
    }
}
